// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


namespace Duende.IdentityServer.Validation;

/// <summary>
/// Compares resource_type strings, where the order of space-delimited values is insignificant.
/// </summary>
/// <remarks>
/// <para>
/// This is to handle the fact that the order of multi-valued response_type lists is
/// insignificant, per the <see href="https://tools.ietf.org/html/rfc6749#section-3.1.1">OAuth2 spec</see>
/// and the
/// (<see href="http://openid.net/specs/oauth-v2-multiple-response-types-1_0-03.html#terminology">OAuth
/// 2.0 Multiple Response Type Encoding Practices draft </see>).
/// </para>
/// </remarks>
public class ResponseTypeEqualityComparer : IEqualityComparer<string>
{
    /// <summary>
    /// Determines whether the specified values are equal.
    /// </summary>
    /// <param name="x">The first string to compare.</param>
    /// <param name="y">The second string to compare.</param>
    /// <returns>true if the specified values are equal; otherwise, false.</returns>
    public bool Equals(string x, string y)
    {
        if (x == y)
        {
            return true;
        }

        if (x == null || y == null)
        {
            return false;
        }

        if (x.Length != y.Length)
        {
            return false;
        }

        var xValues = x.Split(' ');
        var yValues = y.Split(' ');

        if (xValues.Length != yValues.Length)
        {
            return false;
        }

        Array.Sort(xValues);
        Array.Sort(yValues);

        for (var i = 0; i < xValues.Length; i++)
        {
            if (xValues[i] != yValues[i])
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    /// Returns a hash code for the value.
    /// </summary>
    /// <param name="value">The value for which a hash code is to be returned.</param>
    /// <returns>A hash code for the value, suitable for use in hashing algorithms and data structures like a hash table.</returns>
    public int GetHashCode(string value)
    {
        if (value == null)
        {
            return 0;
        }

        var values = value.Split(' ');
        if (values.Length == 1)
        {
            // Only one value, so just spit out the hash code of the whole string
            return value.GetHashCode(StringComparison.InvariantCulture);
        }

        Array.Sort(values);

        // Using Skeet's answer here: http://stackoverflow.com/a/7244729/208990
        // Licensed under Creative Commons CC-BY-SA from SO: https://stackoverflow.com/legal/terms-of-service#licensing
        // Creative Commons CC-BY-SA https://creativecommons.org/licenses/by-sa/4.0/
        var hash = 17;
        foreach (var element in values)
        {
            // changed to use StringComparer.Ordinal, rather than StringComparer.InvariantCulture
            hash = hash * 31 + StringComparer.Ordinal.GetHashCode(element);
        }
        return hash;
    }
}
